#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <pwd.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/wait.h>
#include <stdbool.h>
#include <fcntl.h>
#include <sys/stat.h>
extern char** environ; //用這個打印

#define END_CHAR (strcmp(usr_name,"root") == 0 ? '#' : '$')
#define HOST_NAME_SIZE 20
#define PWD_SIZE 50
#define COMMANDLINE_SIZE 50
#define MY_ENVP_SIZE 50
#define DELIMITER " "
//RDIR是REDIRECT v.重定向的简写
#define IN_RDIR 0 //输入重定向
#define TRUNC_OUT_RDIR 1 //截断式输出重定向
#define APPEND_OUT_RDIR 2 //追加式输出重定向
#define NONE -1 //未重定向
char* usr_name;
char* home_dir;
char commandline[COMMANDLINE_SIZE];
char hostname[HOST_NAME_SIZE];
char pwd[PWD_SIZE];
char* my_envp[MY_ENVP_SIZE];
int my_envp_index;
int lastcode;
int argc = 0;
char* argv[1024];
char* rdir_file_name=NULL;
int rdir;
void write_hostname()
{
	if (gethostname(hostname,HOST_NAME_SIZE))
	{
		perror("gethostname failed");
		exit(-1);
	}
}

void write_pwd()
{
	if (!getcwd(pwd,PWD_SIZE))
	{
		perror("getcwd failed");
		exit(-1);
	}
	if (strlen(home_dir)==strlen(pwd))
	{
		pwd[0]='~';
		pwd[1]='\0';
	}
	else if (strlen(home_dir)<strlen(pwd))
	{
		size_t new_len=strlen(pwd)-strlen(home_dir)+1;
		pwd[0]='~';
		memmove(pwd+1,pwd+strlen(home_dir),new_len);
	}
	else
	{
		//什么都不做
	}
}

void print_command_prompt()
{
	write_pwd();
	printf("%s@%s:%s%c ",usr_name,hostname,pwd,END_CHAR);
}

void set_usr_name()//直接修改全局变量,不用传参
{
	usr_name = getpwuid(getuid()) -> pw_name;
}

void set_home_dir()//直接修改全局变量,不用传参
{
	home_dir = getpwuid(getuid()) -> pw_dir;
}

void check_rdir(char* commandline)
{
	rdir=NONE;//默认未重定向
	char* pos=commandline;
	while(*pos)//循环条件*pos!=\0
	{
		if (*pos=='>')
		{
			//防止越界
			if (pos+1<commandline+strlen(commandline) && *(pos+1)=='>')
			{
				//为>>重定向符号,为追加写入,写入前不会先清空文件
				rdir=APPEND_OUT_RDIR;
				//设置\0,方便split_commandline函数切分字符串
				*pos='\0';
				*(pos+1)='\0';
				//越过已经读取的>>
				pos+=2;
				//避开空格
				while(*pos==' ')
					pos++;
				//剩下文件名
				rdir_file_name=pos;
				return;
			}
			//单个>,为输出重定向,会先清空文件再写入
			rdir=TRUNC_OUT_RDIR;
			//设置\0,方便split_commandline函数切分字符串
			*pos='\0';
			//越过已经读取的>
			pos++;
			//避开空格
			while(*pos==' ')
				pos++;
			//剩下文件名
			rdir_file_name=pos;
			return;
		}
		else if (*pos=='<')
		{
			//单个<,输入重定向
			rdir=IN_RDIR;
			//设置空格,方便split_commandline函数切分字符串
			*pos=' ';
			//越过已经读取的<
			pos++;
			//避开空格
			while(*pos==' ')
				pos++;
			//剩下文件名
			rdir_file_name=pos;
			return;
		}
		else
			pos++;	
	}
}

void get_commandline()
{
	char* fgets_ret=fgets(commandline,COMMANDLINE_SIZE,stdin);
	assert(fgets_ret != NULL);
	fgets_ret[strlen(fgets_ret)-1]='\0';//去掉换行符
	check_rdir(fgets_ret);
	//printf("%s\n",fgets_ret);
}

int split_commandline(char* argv[])
{
	int num=0;
	argv[num++]=strtok(commandline,DELIMITER);
	while (argv[num++]=strtok(NULL,DELIMITER));
	return num-1;	
}

void execute_program()
{
	pid_t fork_ret_id=fork();
	if (fork_ret_id<0)
	{
		perror("fork failed");
		return;
	}
	else if (fork_ret_id==0)//子进程执行命令
	{
		umask(0);
		//需要两个参数argv[0]和rdir_file_name
		if (rdir==IN_RDIR)
		{
			int fd=open(rdir_file_name,O_RDONLY,0666);
			dup2(fd,0);//修改fd_array[0]的指向
		}
		else if (rdir==APPEND_OUT_RDIR)
		{
			int fd=open(rdir_file_name,O_CREAT|O_WRONLY|O_APPEND,0666);
			dup2(fd,1);
		}
		else if (rdir==TRUNC_OUT_RDIR)
		{
			int fd=open(rdir_file_name,O_CREAT|O_WRONLY|O_TRUNC,0666);
			dup2(fd,1);
		}
		else//rdir==NONE
		{
			//什么都不做
		}
		execvpe(argv[0],argv,environ);
		printf("%s: command not found\n",argv[0]);
		exit(-1);
	}
	else //Shell获取到了子进程的PID
	{
		int status;
		pid_t waitpid_ret_id=waitpid(fork_ret_id,&status,0);//阻塞等待
		if (waitpid_ret_id<0)
		{
			perror("waitpid failed");
			return;
		}
		if (fork_ret_id==waitpid_ret_id)
		{
			lastcode = WEXITSTATUS(status);
		}
	}
}

bool execute_buildin_command(int argc,char* argv[])
{
	if (argc==2&&strcmp(argv[0],"cd")==0)
	{
		if (chdir(argv[1])<0)
			perror("change working directory failed");
		return true;
	}
	if (argc==1&&strcmp(argv[0	],"env")==0)
	{
		for (int i=0;environ[i];i++)
			printf("%s\n",environ[i]);
		return true;
	}
	if (strcmp(argv[0],"about")==0)
	{
		printf("DIY Shell v2.0 Written By zhangcoder~\n");
		return true;
	}
	if (argc==2&&strcmp(argv[0],"export")==0)
	{
		char* tmp=(char*)malloc(strlen(argv[1])+1);
		strcpy(tmp,argv[1]);
		//putenv("A=aaa");
		my_envp[my_envp_index]=tmp;
		if (putenv(my_envp[my_envp_index])!=0)
			perror("change or add an environment variable failed");
		my_envp_index++;
		return true;
	}
	if (argc==2&&strcmp(argv[0],"echo")==0)
	{
		if (strcmp(argv[1],"$?")==0)
		{
			printf("%d\n",lastcode);
			lastcode=0;//echo $?正常执行
			return true;
		}
		else//argv[1]=="$"
		{
			printf("%s\n",getenv((char*)argv[1]+1));
			return true;
		}
	}
	return false;
}

int main()
{
	set_home_dir();
	set_usr_name();
	write_hostname();
	my_envp_index=0;
	while (1)
	{
		rdir_file_name=NULL;
		print_command_prompt();
		get_commandline();
		

		int argc=split_commandline(argv);
		if (argv[0]==NULL)//不加会报段错误
			continue;
		if (!strcmp(argv[0],"exit"))
			break;
		if (strcmp(argv[0],"ls")==0)//为ls加上颜色选项
		{
			argv[argc++]="--color";
			argv[argc]=NULL;
		}
		bool is_buildin=execute_buildin_command(argc, argv);
		if (!is_buildin)
			execute_program(argc,argv);
	}
	return 0;
}
